iT邦幫忙

2022 iThome 鐵人賽

DAY 13
0
自我挑戰組

30天演算法解題系列 第 13

Day 13:Caesar cipher encryptor

  • 分享至 

  • xImage
  •  

problem

輸入為一個正整數 k,以及一個字串,字串不為空且其中字元都是小寫英文字母。將字串中每個字元在字母表中移動 k 個位置,回傳得到的新字串。

字元移動時需在字母表中循環,例如 'z' 移動一個位置應回傳 'a'。

sample input:
string = 'xyz'
k = 2

sample output:
'zab'

solution 01

基本的想法是,遍歷過字串,對每個字母進行移位的運算後,放進一個陣列中,最後再將陣列元素合併為新字串。

而移位運算的部分,可以使用 Unicode 進行。大部分的程式語言都有內建的函式可以轉換 Unicode 和字元,可以以此來進行運算。

虛擬碼來表示的話,若將字母移位時,在代碼上的變化可以理解為:

// 假設 toCode() 將字母轉為代碼
newCode = toCode(letter) + k

接下來要將新代碼轉回字母,且若超過字母表範圍,要再從 a 開始。可以以 a 和 z 的代碼 (97, 122) 以及餘數運算子 (%) 表達。

// 假設 toLetter() 將代碼轉為字母
if (newCode <= 122) 
    return toLetter(newCode)
else
    return toLetter(96 + newCode % 122)

最後要考慮的是如果 k 是一個較大的數字可能會出現的問題。例如說當 k = 54,其實結果應該要跟 k = 2 一樣,因為每移動 26 個位置就會回到原本的字母。

但是用 54 運算的話,就算最後 % 122 還是會超出字母表的範圍。所以在一開始再加上以下一行,來避免錯誤:

k = k % 26

n 為字串長度
time: O(n)
space: O(n)

function caesarCipherEncryptor(string, k) {
  const newLetters = [];
  const newK = k % 26;
  for (const letter of string) {
    newLetters.push(getNewLetter(letter, newK));
  }
  return newLetters.join('');
}

function getNewLetter(letter, key) {
  const newCode = letter.charCodeAt() + key;
  return newCode <= 122 ? String.fromCharCode(newCode) : String.fromCharCode(96 + newCode % 122);
}

solution 02

第二種解算是第一種的延伸,如果 Unicode 做的事情就是將字母跟代碼做連結,那麼也可以嘗試自己創造代碼,例如把字母放入陣列,以索引值作為字母對應的數字。若 a 的代碼為 0,z 的代碼為 25,那麼前述的虛擬碼可以修改為:

// 假設 alphabet.indexOf() 回傳字母表中索引
k = k % 26
newCode = alphabet.indexOf(letter) + k
return toLetter(newCode % 26)

這裡可以特別注意的是,虛擬碼並不是比照 Unicode 的版本,在 newCode > 25 的情況下回傳 toLetter(-1 + newCode % 25),是因為如果 newCode 整除 25,就會得到索引值為 -1 的錯誤。例如以下的輸入:

string = "z"
key = 25

前面 Unicode 的版本之所以不會有這個問題,是因為用來運算出 newCode 的 k 一定小於 26,且區塊內 newCode 一定大於 122,所以 newCode % 122 一定不會等於 0。

這個解法的時間和空間複雜度一樣都是 O(n)。另外可以特別提到的是使用 alphabet.indexOf 這樣的運算時,若字母表如本題只有 26 個字母,就只需要常數時間。但字母表若比較大,例如長度為 m,則運算需要 O(m) 時間。

function caesarCipherEncryptor(string, k) {
  const newLetters = [];
  const newK = k % 26;
  const alphabet = 'abcdefghijklmnopqrstuvwxyz'.split('');
  for (const letter of string) {
    newLetters.push(getNewLetter(letter, newK, alphabet));
  }
  return newLetters.join('');
}

function getNewLetter(letter, key, alphabet) {
  const newCode = alphabet.indexOf(letter) + key;
  return alphabet[newCode % 26];
}

上一篇
Day 12:first non-repeating character
下一篇
Day 14:run-length encoding
系列文
30天演算法解題30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言